/*global define, alert, Howl */

define(['controls', 'player', 'platform', 'megaplatform', 'coin', 'goal'], function(controls, Player, Platform, MegaPlatform, Coin, Goal) {

  var transform = $.fx.cssPrefix + 'transform';

  // How far from each edge should the player be.
  var VIEWPORT_PADDING = 300;
  var ORIGINAL_CONTAINER_TOP_POS = 1000;
  /**
   * Main game class.
   * @param {Element} el DOM element containig the game.
   * @constructor
   */
  var Game = function(el) {
    this.el = el;
    this.containerEl = el.find('.container');
    this.worldEl = el.find('.world');
    this.platformsEl = el.find('.platforms');
    this.megaPlatformsEl = el.find('.megaplatforms');
    this.coinsEl = el.find('.coins');
    this.goalsEl = el.find('.goals');
    this.scoreEl = el.find('.score .value');
    this.entities = [];
    this.player = new Player(this.el.find('.player'), this);

    this.sound = new Howl({
      urls: ['sounds/main.mp3', 'sounds/main.ogg'],
      sprite: {
        blast: [0, 2000],
        laser: [3000, 700],
        winner: [5000, 9000]
      }
    });
    
    // Cache a bound onFrame since we need it each frame.
    this.onFrame = this.onFrame.bind(this);
  };

  /**
   * Reset all game state for a new game.
   */
  Game.prototype.reset = function() {
    // Reset world.
    this.entities.forEach(function(e) { e.el.remove(); });
    this.entities = [];
    this.createWorld();
    this.player.pos = {x: 700, y: 2715};

    // Reset viewport.
    this.viewport = {x: 0, y: 0, width: 1210, height:5000};

    // Coins
    this.collectedCoins = 0;
    this.scoreEl.text(0);

    // Start game
    this.unfreezeGame();
  };

  Game.prototype.createWorld = function() {
    // Ground.
    this.addPlatform(new Platform({
      x: 100,
      y: 2918,
      width: 800,
      height: 10
    }));

    // Floating platforms

    var atY = 2900;
    var atX = 500;
    var n = 28; //number of platforms
    for ( c = 1; c < n; c++ ) {    
      atX = atX + ((Math.random()-0.5)*400);
      //Ugly hack to avoid going out of frame
      if (atX > 1000){
        atX = 900
      } 
      else if (atX < 0){
        atX = 100;
      }    
      atY = atY - 100; 
      this.addPlatform(new Platform({
      x: atX,
      y: atY,
      width: 100,
      height: 10
    }));
    }

    // Randim megaplatforms
    atX = 500;
    atY = 2900;
    n = 5;
    for ( c = 1; c < n; c++ ) {    
      atX = atX + ((Math.random()-0.5)*400);
      //Ugly hack to avoid going out of frame
      if (atX > 1000){
        atX = 900
      } 
      else if (atX < 0){
        atX = 100;
      }    
      atY = atY - 1000; 
      this.addMegaPlatform(new MegaPlatform({
      x: atX,
      y: atY,
      width: 100,
      height: 10
    }));
    }
    

    // Coins
    this.addCoin(new Coin({
      x: 770,
      y: 80
    }));
    this.addCoin(new Coin({
      x: 20,
      y: 2780
    }));

    // Add goal
    this.addGoal(new Goal({
       x: 190,
       y: 50
    }));
  };

  Game.prototype.addPlatform = function(platform) {
    this.entities.push(platform);
    this.platformsEl.append(platform.el);
  };

  Game.prototype.addMegaPlatform = function(megaPlatform) {
    this.entities.push(megaPlatform);
    this.megaPlatformsEl.append(megaPlatform.el);
  };

  Game.prototype.addCoin = function(coin) {
    this.entities.push(coin);
    this.coinsEl.append(coin.el);
  };

    Game.prototype.addGoal = function(goal) {
    this.entities.push(goal);
    this.goalsEl.append(goal.el);
  };

  Game.prototype.hitCoin = function(coin) {
    this.collectedCoins++;
    this.scoreEl.text(this.collectedCoins);
    coin.hit();
  };

  Game.prototype.hitGoal = function(goal) {
    this.gameover('Goal reached - you have won the game!');
  };


  Game.prototype.forEachPlatform = function(fun) {
    for (var i = 0, e; e = this.entities[i]; i++) {
      if (e instanceof Platform) {
        fun(e);
      }
    }
  };

  Game.prototype.forEachMegaPlatform = function(fun) {
    for (var i = 0, e; e = this.entities[i]; i++) {
      if (e instanceof MegaPlatform) {
        fun(e);
      }
    }
  };

  Game.prototype.forEachCoin = function(fun) {
    for (var i = 0, e; e = this.entities[i]; i++) {
      if (e instanceof Coin) {
        fun(e);
      }
    }
  };

    Game.prototype.forEachGoal = function(fun) {
    for (var i = 0, e; e = this.entities[i]; i++) {
      if (e instanceof Goal) {
        fun(e);
      }
    }
  };

  /**
   * Runs every frame. Calculates a delta and allows each game entity to update itself.
   */
  Game.prototype.onFrame = function() {
    if (!this.isPlaying) {  
      return;
    }

    var now = +new Date() / 1000,
        delta = now - this.lastFrame;
    this.lastFrame = now;

    controls.onFrame(delta);
    this.player.onFrame(delta);

    for (var i = 0, e; e = this.entities[i]; i++) {
      e.onFrame(delta);

      if (e.dead) {
        this.entities.splice(i--, 1);
      }
    }

    this.updateViewport();

    // Request next frame.
    requestAnimFrame(this.onFrame);

    this.elevate();

  };

  Game.prototype.elevate = function() {
    document.getElementsByClassName('container')[0].style.top = ORIGINAL_CONTAINER_TOP_POS - this.player.pos.y + 'px';
  };  

  Game.prototype.updateViewport = function() {
    // Find min and max X for player in world coordinates.
    var minX = this.viewport.x + VIEWPORT_PADDING;
    var maxX = this.viewport.x + this.viewport.width - VIEWPORT_PADDING;

    // Player position
    var playerX = this.player.pos.x;

    //Update the viewport if needed.
    if (playerX < minX) {
      this.viewport.x = playerX - VIEWPORT_PADDING;
    } else if (playerX > maxX) {
      this.viewport.x = playerX + VIEWPORT_PADDING - this.viewport.width;
    }

    // Let's not go outside the level.
    // TODO: Level end?
    this.viewport.x = Math.max(0, this.viewport.x);
    
    this.worldEl.css(transform, 'translate(' + (-this.viewport.x) + 'px, 0)');
  };

  /**
   * Starts the game.
   */
  Game.prototype.start = function() {
    this.reset();
  };

  /**
   * Stop the game and notify user that he has lost.
   */
  Game.prototype.gameover = function(msg) {
    alert(msg);
    this.freezeGame();

    var game = this;
    setTimeout(function() {
      game.reset();
    }, 0);
  };

  /**
   * Freezes the game. Stops the onFrame loop and stops any CSS3 animations.
   * Can be used both for game over and pause.
   */
  Game.prototype.freezeGame = function() {
    this.isPlaying = false;
    this.el.addClass('frozen');
  };

  /**
   * Unfreezes the game. Starts the game loop again.
   */
  Game.prototype.unfreezeGame = function() {
    if (!this.isPlaying) {
      this.isPlaying = true;
      this.el.removeClass('frozen');

      // Restart the onFrame loop
      this.lastFrame = +new Date() / 1000;
      requestAnimFrame(this.onFrame);
    }
  };

  /**
   * Cross browser RequestAnimationFrame
   */
  var requestAnimFrame = (function() {
    return window.requestAnimationFrame ||
        window.webkitRequestAnimationFrame ||
        window.mozRequestAnimationFrame ||
        window.oRequestAnimationFrame ||
        window.msRequestAnimationFrame ||
        function(/* function */ callback) {
          window.setTimeout(callback, 1000 / 60);
        };
  })();

  return Game;
});